﻿using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using SdmxMl.Structure;
using SdmxMl.Common;
using SdmxMl.Manager;

namespace ActivityMgr.HGDE
{
    /// <summary>
    /// Not generic.
    /// Helper class for SFSO NSI for Historized municipalities correspondances.
    /// </summary>
    public class HclCorrespondance
    {
        #region Internal Members

        /// <summary>Artefacts container</summary>
        private SmManager _sm;

        /// <summary>Correspondence Start and Stop date</summary>
        private DateTime _startDate;
        private DateTime _stopDate;
        private Dictionary<string, CodeListType> _dicoClRef;
        private Dictionary<string, InscriptionRadiationPair> _irDicoLevel;
        private Dictionary<int, List<TimedCodeType>> _dicoTimeCodeList;
        private HierarchicalCodelistType _hcl;
        private HierarchyType _hierarchy;
        private DateTime _defaultValidFromDate;
        private bool _showAll;
        private bool _showAssociatedText;
        private bool _showParent;
        private bool _showOnlyNonHistorizedCodeChanges;


        #endregion

        #region Properties

        public SmManager SM
        {
            get { return _sm; }
        }
        #endregion

        #region C'tor

        public HclCorrespondance(string fullId, SmManager sm)
        {
            if (sm == null)
                _sm = new SmManager();
            else
                _sm = sm;


            // Now try to fetch Hierarchical Code List
            ArtefactRefTyped art = new ArtefactRefTyped(SdmxArtefactType.HierarchicalCodelists, fullId);
            ArtefactRefTypedList artList = new ArtefactRefTypedList();
            artList.Add(art);
            _sm.LoadMissingArtefactsFromReferenceMgr(artList);
            if (art.IsLoaded == false)
                _sm.LoadMissingArtefactsFromRegistry(artList);
            if (art.IsLoaded == false)
                throw new ApplicationException("Unknown Hierarchical Codelist: " + fullId);

            // Now fetch Refered artefacts
            _hcl = _sm.GetArtefact(art) as HierarchicalCodelistType;
            artList = _hcl.ArtefactRefList;
            _sm.LoadMissingArtefactsFromReferenceMgr(artList);
            _sm.LoadMissingArtefactsFromRegistry(artList);


            if (artList.FullyLoaded == false)
                throw new ApplicationException("Some refered artefacts are not available!" +
                    Environment.NewLine + artList.MissingListMessage);

            // get hierarchy to work with
            if (_hcl.HierarchyList == null || _hcl.HierarchyList.Count == 0)
                throw new ApplicationException("No hierarchy defined for: " + _hcl.FullIdent);
            _hierarchy = _hcl.HierarchyList[0];
            _dicoClRef = GetRefDictionary(_hierarchy);

            // Determine default start time
            try
            {
                _defaultValidFromDate = DateTime.Parse(_hcl.ValidFrom);
            }
            catch
            {
                throw new ApplicationException("No valid attribute 'ValidFrom' defined in hierarchy");
            }

            // Dictionary for considered codes at each level
            _dicoTimeCodeList = new Dictionary<int, List<TimedCodeType>>();

        }

        #endregion

        public void PrepareTimeFrame(DateTime start, DateTime stop, bool showAll, bool showText, bool showParent, bool showOnlyNonHistChg)
        {
            _startDate = start;
            _stopDate = stop;
            _showAll = showAll;
            _showAssociatedText = showText;
            _showParent = showParent;
            _showOnlyNonHistorizedCodeChanges = showOnlyNonHistChg;

            if (_startDate < _defaultValidFromDate)
                _startDate = _defaultValidFromDate;

            TreeRecurse(_hcl.HierarchyList[0].CodeRefList, 0, null);

            _irDicoLevel = ConstructInscriptionRadiationDicoList();

        }

        private void TreeRecurse(CodeRefTypeList orgList, int level, CodeType parentCode)
        {
            foreach (CodeRefType crt in orgList)
            {

                DateTime dtFrom, dtTo;
                DetermineLimitsDate(crt, out dtFrom, out dtTo);
                TimePosition tp = DetermineTimeFrame(dtFrom, dtTo);
                CodeType c = null;
                // Do we need to proceed
                if (tp != TimePosition.Outside)
                {
                        CodeListType clt = _dicoClRef[crt.CodelistAliasRef];

                        c = clt.CodeList.LocateItem(crt.CodeID) as CodeType;
                        if (c == null)
                            throw new ApplicationException("Undiscoverable code: " + crt.CodeID);

                        // Create Item and put in level list
                        TimedCodeType tct = new TimedCodeType(crt.CodelistAliasRef, dtFrom, dtTo, c, parentCode, tp);
                        if (_dicoTimeCodeList.ContainsKey(level) == false)
                            _dicoTimeCodeList.Add(level, new List<TimedCodeType>(256));
                        _dicoTimeCodeList[level].Add(tct);

                    // Recurse
                    TreeRecurse(crt.CodeRefList, level + 1, c);
                }
            }

        }

        private TimePosition DetermineTimeFrame(DateTime dtFrom, DateTime dtTo)
        {
            TimePosition tp = TimePosition.Outside;

            if (dtTo < _startDate || dtFrom > _stopDate )
                tp = TimePosition.Outside;
            else if (dtFrom > _startDate  && dtTo < _stopDate)
                tp = TimePosition.Inside;
            else if (dtFrom <= _startDate && dtTo >= _stopDate)
                tp = TimePosition.Constant;
            else if (dtTo < _stopDate)
                tp = TimePosition.StopInside;
            else if (dtFrom > _startDate)
                tp = TimePosition.StartInside;

            return tp;
        }

        private void DetermineLimitsDate(CodeRefType crt, out DateTime dtFrom, out DateTime dtTo)
        {
            try
            {
                dtFrom = DateTime.MaxValue;
                if (string.IsNullOrEmpty(crt.ValidFrom) == false)
                    dtFrom = Convert.ToDateTime(crt.ValidFrom);
                else
                    dtFrom = _defaultValidFromDate;

                dtTo = DateTime.MaxValue;
                if (string.IsNullOrEmpty(crt.ValidTo) == false)
                    dtTo = Convert.ToDateTime(crt.ValidTo);
            }
            catch
            {
                throw new ApplicationException("Invalid Date format in ValidFrom / ValidTo attributes.");
            }
        }

        private Dictionary<string, InscriptionRadiationPair> ConstructInscriptionRadiationDicoList()
        {
            Dictionary<string, InscriptionRadiationPair> dico =
                new Dictionary<string, InscriptionRadiationPair>(_dicoClRef.Count);

            foreach (KeyValuePair<string, CodeListType> kvp in _dicoClRef)
            {
                //   CodeListType clType = _dicoClRef[clrefType.Alias];
                dico.Add(kvp.Key, new InscriptionRadiationPair(
                    new DicoIRRef(512),
                    new DicoIRRef(512)));
            }

            // Now create content
            foreach (List<TimedCodeType> lt in _dicoTimeCodeList.Values)
            {
                foreach (TimedCodeType tct in lt)
                {
                    InscriptionRadiationPair irp = dico[tct.clRef];
                    if (tct.Inscription.Length > 0)
                    {
                        if (irp.Inscription.ContainsKey(tct.Inscription) == false)
                            irp.Inscription.Add(tct.Inscription, new List<TimedCodeType>());
                        irp.Inscription[tct.Inscription].Add(tct);
                    }
                    if (tct.Radiation.Length > 0)
                    {
                        if (irp.Radiation.ContainsKey(tct.Radiation) == false)
                            irp.Radiation.Add(tct.Radiation, new List<TimedCodeType>());
                        irp.Radiation[tct.Radiation].Add(tct);
                    }
                }
            }


            return dico;
        }

        #region Ref Dictionary

        private Dictionary<string, CodeListType> GetRefDictionary(HierarchyType heType)
        {
            Dictionary<string, CodeListType> dico = new Dictionary<string, CodeListType>();

            foreach (CodelistRefType crt in heType.CodelistRefList)
            {
                CodeListType clType = null;
                ArtefactRefBase arb = new ArtefactRefBase(crt.Id, crt.AgencyId, crt.Version);
                clType = _sm.GetArtefact(SdmxArtefactType.CodeLists, arb) as CodeListType;
                if (clType != null)
                    dico.Add(crt.Alias, clType);
                else
                    throw new ApplicationException("Invalid CodelistRef, not found: " + arb.FullIdent);
            }

            return dico;
        }

        #endregion

        public DataTable CreateLevelTable(int level, string name)
        {
            DataTable t = new DataTable(name);
            t.Columns.Add(new DataColumn("OldHistCode", typeof(string)));
            t.Columns.Add(new DataColumn("NewHistCode", typeof(string)));
            t.Columns.Add(new DataColumn("OldCode", typeof(string)));
            t.Columns.Add(new DataColumn("NewCode", typeof(string)));
            if (_showParent)
            {
            t.Columns.Add(new DataColumn("OldParentHistCode", typeof(string)));
            t.Columns.Add(new DataColumn("NewParentHistCode", typeof(string)));
            }
            if (_showAssociatedText)
            {
                t.Columns.Add(new DataColumn("OldName", typeof(string)));
                t.Columns.Add(new DataColumn("NewName", typeof(string)));
            }

            if (_showParent && _showAssociatedText)
            {
                t.Columns.Add(new DataColumn("OldParentName", typeof(string)));
                t.Columns.Add(new DataColumn("NewParentName", typeof(string)));
            }

            if (_dicoTimeCodeList.ContainsKey(level) == false)
                throw new ApplicationException("Invalid level index: " + level.ToString());

            foreach (TimedCodeType tc in _dicoTimeCodeList[level])
            {
                try
                {
                    if (tc.Treated == false)
                    {
                        if (tc.TimePos == TimePosition.Constant)
                        {
                            if (_showAll)
                                RecordRowStartStop(tc, tc, t);
                        }
                        else if (tc.TimePos == TimePosition.StartInside)
                        {
                            List<TimedCodeType> lt = _irDicoLevel[tc.clRef].Radiation[tc.Inscription];
                            if (lt == null)
                                throw new ApplicationException("Unexpected empty radiation list for:" + tc.Code.Id);

                            ManagePrevious(t, tc, lt);
                        }
                        else if (tc.TimePos == TimePosition.StopInside)
                        {
                            List<TimedCodeType> ltnext = _irDicoLevel[tc.clRef].Inscription[tc.Radiation];
                            if (ltnext == null)
                                throw new ApplicationException("Unexpected empty inscription list for:" + tc.Code.Id);

                            ManageNext(t, tc, ltnext);
                        }
                    }
                }
                catch (Exception ex)
                {
                    string s = ex.Message;
                }

            }
            return t;
        }

        private void RecordRowStartStop(TimedCodeType tstart, TimedCodeType tstop, DataTable table)
        {
            tstart.Treated = tstop.Treated = true;

            // If only changes on snapshot code
            if (_showOnlyNonHistorizedCodeChanges &&
                tstart.NonHistCode == tstop.NonHistCode)
                return;

            DataRow row = table.Rows.Add(new object[] 
                { 
                    tstart.Code.Id,
                    tstop.Code.Id,
                    tstart.NonHistCode,
                    tstop.NonHistCode
                });

            int i = 4;
            if (_showParent)
            {
                row[i++] = tstart.ParentCode != null ? tstart.ParentCode.Id : string.Empty;
                row[i++] = tstop.ParentCode != null ? tstop.ParentCode.Id : string.Empty;
            }

            if (_showAssociatedText)
            {
                row[i++] = tstart.Code.Name.ToString();
                row[i++] = tstop.Code.Name.ToString();
            }
            if (_showParent && _showAssociatedText)
            {
                row[i++] = tstart.ParentCode != null ? tstart.ParentCode.Name.ToString() : string.Empty;
                row[i++] = tstop.ParentCode != null ? tstop.ParentCode.Name.ToString() : string.Empty;
            }
        }

        private void ManagePrevious(DataTable table, TimedCodeType tc, List<TimedCodeType> lt)
        {
            foreach (TimedCodeType tprev in lt)
            {
                if (tprev.TimePos == TimePosition.StopInside)
                {
                    if (tc.TimePos == TimePosition.StartInside)
                        RecordRowStartStop(tprev, tc, table);
                    else
                    {
                        // Current was totally inside
                        if (tc.TimePos != TimePosition.Inside)
                            throw new ApplicationException("Unexpected, current for 'manage previous request' not in (Start inside or totally inside:" + tc.Code.Id);

                        List<TimedCodeType> ltnext = _irDicoLevel[tc.clRef].Inscription[tc.Radiation];
                        if (ltnext == null)
                            throw new ApplicationException("Unexpected empty inscription list for:" + tc.Code.Id);

                        ManageNext(table, tc, ltnext);

                    }
                }
                else // tprev is totally inside
                {
                    if (tprev.TimePos != TimePosition.Inside)
                        throw new ApplicationException("Unexpected, prev for 'manage previous request' not totally inside:" + tc.Code.Id);

                    List<TimedCodeType> ltprev = _irDicoLevel[tprev.clRef].Radiation[tprev.Inscription];
                    if (ltprev == null)
                        throw new ApplicationException("Unexpected empty radiation list for:" + tprev.Code.Id);

                    ManagePrevious(table, tc, ltprev);
                }
            }

        }
        
        // In next we assume start already found 
        private void ManageNext(DataTable table, TimedCodeType tstart, List<TimedCodeType> lt)
        {
            foreach (TimedCodeType tnext in lt)
            {
                if (tnext.TimePos == TimePosition.StartInside)
                    RecordRowStartStop(tstart, tnext, table);

                else // tnext is totally inside
                {
                    if (tnext.TimePos != TimePosition.Inside)
                        throw new ApplicationException("Unexpected next not in (start inside, or fully inside: " + tnext.Code.Id);
                    //Manage Next again
                    List<TimedCodeType> ltnext = _irDicoLevel[tnext.clRef].Inscription[tnext.Radiation];
                    if (ltnext == null)
                        throw new ApplicationException("Unexpected empty inscription list for:" + tnext.Code.Id);

                    ManageNext(table, tstart, ltnext);

                }
            }
        }

    }


    #region Helper Classes

    public class DicoIRRef : Dictionary<string, List<TimedCodeType>>
    {
        public DicoIRRef(int capacity) : base (capacity) { }
    }

    public class InscriptionRadiationPair
    {
        public DicoIRRef Inscription;
        public DicoIRRef Radiation;

        public InscriptionRadiationPair(DicoIRRef inscr, DicoIRRef rad)
        {
            Inscription = inscr;
            Radiation = rad;
        }
    }

    #endregion

}
